package com.gitee.easyopen.support;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.interfaces.RSAPrivateKey;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.OAuthResponse;
import org.apache.velocity.VelocityContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.gitee.easyopen.ApiConfig;
import com.gitee.easyopen.ApiContext;
import com.gitee.easyopen.ApiResult;
import com.gitee.easyopen.Encrypter;
import com.gitee.easyopen.Invoker;
import com.gitee.easyopen.ParamNames;
import com.gitee.easyopen.auth.Oauth2Manager;
import com.gitee.easyopen.auth.Oauth2Service;
import com.gitee.easyopen.bean.Consts;
import com.gitee.easyopen.bean.RequestMode;
import com.gitee.easyopen.doc.ApiDocBuilder;
import com.gitee.easyopen.doc.ApiDocHolder;
import com.gitee.easyopen.exception.LoginErrorException;
import com.gitee.easyopen.message.Errors;
import com.gitee.easyopen.register.AbstractInitializer;
import com.gitee.easyopen.util.RequestUtil;
import com.gitee.easyopen.util.VelocityUtil;

/**
 * 提供API访问能力,新建一个类继承这个即可.RequestMapping中的value自己定义
 * 
 * <pre>
 * &#64;Controller
 * &#64;RequestMapping(value = "/api")
 * public class IndexController extends ApiController {
 * }
 * 
 * 这样接口的统一访问路径为:http://ip:port/contextPath/api
 * </pre>
 */
public abstract class ApiController extends AbstractInitializer implements ApplicationListener<ContextRefreshedEvent> {

    protected ApiConfig apiConfig;
    
    @Autowired(required = false)
    protected Oauth2Manager oauth2Manager;
    
    protected Oauth2Service oauth2Service;
    
    protected Invoker invoker;

    // spring容器加载完毕后执行
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        this.apiConfig = new ApiConfig();
        
        apiConfig.loadPrivateKey();
        
        this.initApiConfig(this.apiConfig);

        this.init(event.getApplicationContext(), this.apiConfig);
        
        oauth2Service = apiConfig.initOauth2Service(oauth2Manager);
        invoker = this.apiConfig.getInvoker();
    }

    /**
     * 请求入口
     * 
     * @param request
     * @param response
     * @throws Throwable
     */
    @PostMapping
    public void index(HttpServletRequest request, HttpServletResponse response) throws Throwable {
        // 调用接口方法,即调用被@Api标记的方法
        this.invoker.invoke(request, response);
    }
    
    @PostMapping("ssl")
    public void ssl(HttpServletRequest request, HttpServletResponse response) throws Throwable {
        ApiContext.setRequestMode(RequestMode.ENCRYPT);
        this.invoker.invoke(request, response);
    }

    /**
     * 文档页面
     * 
     * @param request
     * @param response
     * @throws Throwable
     */
    @RequestMapping("doc")
    public void doc(HttpServletRequest request, HttpServletResponse response) throws Throwable {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=UTF-8");
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        if (!this.apiConfig.isShowDoc()) {
            response.getWriter().write("文档功能未开启");
            return;
        }

        ClassPathResource res = new ClassPathResource(this.apiConfig.getDocClassPath());

        VelocityContext context = new VelocityContext();
        context.put("title", "API文档");
        ApiDocBuilder docBuilder = ApiDocHolder.getApiDocBuilder();
        context.put("docEntrys", docBuilder.getDocItemEntry());
        String url = request.getRequestURL().toString();
        context.put("url", url.substring(0, url.length() - 4));
        
        context.put("ctx", request.getContextPath());
        
        context.put("ACCESS_TOKEN_NAME", ParamNames.ACCESS_TOKEN_NAME);
        context.put("API_NAME", ParamNames.API_NAME);
        context.put("APP_KEY_NAME", ParamNames.APP_KEY_NAME);
        context.put("DATA_NAME", ParamNames.DATA_NAME);
        context.put("FORMAT_NAME", ParamNames.FORMAT_NAME);
        context.put("SIGN_METHOD_NAME", ParamNames.SIGN_METHOD_NAME);
        context.put("SIGN_NAME", ParamNames.SIGN_NAME);
        context.put("TIMESTAMP_NAME", ParamNames.TIMESTAMP_NAME);
        context.put("TIMESTAMP_PATTERN", ParamNames.TIMESTAMP_PATTERN);
        context.put("VERSION_NAME", ParamNames.VERSION_NAME);
        
        this.processDocVelocityContext(context);

        VelocityUtil.generate(context, res.getInputStream(), response.getWriter());
    }
    
    /**
     * 对doc模板做额外处理
     * @param context
     */
    protected void processDocVelocityContext(VelocityContext context) {
        
    }
    
    /**
     * 交换随机码
     * @param request
     * @param response
     * @return
     * @throws Throwable
     */
    @RequestMapping("handshake")
    @ResponseBody
    public Object handshake(HttpServletRequest request, HttpServletResponse response) throws Throwable {
        try {
            String randomKeyEncrypted = RequestUtil.getText(request);
            Encrypter encrypter = this.apiConfig.getEncrypter();
            String privateKey = this.apiConfig.getPrivateKey();
            if(StringUtils.isEmpty(privateKey)) {
                logger.error("未设置正确的私钥");
                throw Errors.ERROR_SSL.getException();
            }
            RSAPrivateKey pk = encrypter.getPrivateKey(privateKey);
            // 得到客户端传来的随机码
            String randomKey = encrypter.rsaDecryptByPrivateKey(randomKeyEncrypted, pk);
            request.getSession().setAttribute(Consts.RANDOM_KEY_NAME, randomKey);
            // 再用随机码进行加密,返回给客户端
            // 客户端如果能解开,说明两边对接成功
            // 后续的数据传输都通过这个随机码进行加解密操作
            String retContent = "0";
            String aesStr = encrypter.aesEncryptToHex(retContent, randomKey);
            
            String data = encrypter.rsaEncryptByPrivateKey(aesStr, pk);
            
            ApiResult result = new ApiResult();
            result.setData(data);
            
            return result;
        }catch (Exception e) {
            logger.error(e.getMessage(), e);
            ApiResult result = new ApiResult();
            result.setCode(-1);
            result.setMsg(e.getMessage());
            return result;
        }
    }

    /**
     * oauth2认证获取code
     * @param request
     * @param resp
     * @return
     * @throws URISyntaxException
     * @throws OAuthSystemException
     */
    @RequestMapping("authorize")
    public Object authorize(HttpServletRequest request,HttpServletResponse resp) throws URISyntaxException, OAuthSystemException {
        try {
            OAuthResponse response = oauth2Service.authorize(request, resp, apiConfig);
            HttpHeaders headers = new HttpHeaders();
            headers.setLocation(new URI(response.getLocationUri()));
            return new ResponseEntity<String>(headers, HttpStatus.valueOf(response.getResponseStatus()));
        } catch (LoginErrorException e) {
            return null;
        }
    }

    /**
     * 通过code获取accessToken
     * @param request
     * @return
     * @throws URISyntaxException
     * @throws OAuthSystemException
     */
    @RequestMapping(value = "accessToken", method=RequestMethod.POST)
    public HttpEntity<?> accessToken(HttpServletRequest request) throws URISyntaxException, OAuthSystemException {
        OAuthResponse response = oauth2Service.accessToken(request, apiConfig);
        return new ResponseEntity<String>(  
                response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); 
    }
    
    // 捕捉异常
    @ExceptionHandler(value = Throwable.class)
    public void jsonErrorHandler(HttpServletRequest req, HttpServletResponse response, Throwable e) throws Exception {
        this.invoker.caugthException(response, e);
    }
    
    protected abstract void initApiConfig(ApiConfig apiConfig);

}
